
#include "web.h"
#include "../socket_client/client.h"
#include <sys/select.h>

// lib function
struct addrinfo * host_serv(const char * host, const char * serv, int family, int socktype)
{
    struct addrinfo * result;
    struct addrinfo hints;
    bzero(&hints, sizeof(hints));

    hints.ai_flags    = AI_CANONNAME;
    hints.ai_family   = family;
    hints.ai_socktype = socktype; 

    int n = 0;
    if((n = getaddrinfo(host, serv, &hints, &result)) != 0)
    {
        // failure
        return NULL;
    }
    return result;
}

static void write_get_cmd(struct file * fptr)
{
    char line[MAXLINES];
    int n = snprintf(line, sizeof(line), GET_CMD, fptr->f_name);

    write(fptr->f_fd, line, n);

    printf("wrote %d bytes for %s\n", n, fptr->f_name);
    fptr->f_flags = F_READING;

    FD_SET(fptr->f_fd, &read_set);
    if(fptr->f_fd > maxfd)
    {
        maxfd = fptr->f_fd;
    }
}

int main(int argc, char * argv[])
{
    fd_set readset, writeset;
    if(argc < 5)
    {
        printf("usage: web <#conns> <hostname> <homepage> <file1> ...\n");
        return EXIT_FAILURE;
    }
    int maxnconn = atoi(argv[1]);
    nfiles       = min(argc - 4, MAXLINES);
    for(int i = 0; i < nfiles; i++)
    {
        file[i].f_name = argv[i + 4];
        file[i].f_host = argv[2];
        file[i].f_flags = 0;
    }
    printf("nfiles = %d.\n",nfiles);
    home_page(argv[2],argv[3]);

    FD_ZERO(&read_set);
    FD_ZERO(&write_set);

    maxfd = -1;
    nlefttoread = nlefttoconn = nfiles;

    int flags = 0;
    int error = 0;
    char buf[BUFFERSIZE];
    while(nlefttoread > 0)
    {
        while(nconn < maxnconn && nlefttoconn > 0)
        {
            int i = 0;
            for(i = 0; i < nfiles; i++)
            {
                // 校验
                if(file[i].f_flags == 0)
                {
                    break;
                }
            }
            // 完成
            if(i == nfiles)
            {
                printf("nleft to connect = %d but nothing found", nlefttoconn);
                return EXIT_FAILURE;
            }
            start_connect(&file[i]);

            nconn = nconn + 1;
            nlefttoconn = nlefttoconn - 1;
        }
        readset  = read_set;
        writeset = write_set;

        int n = select(maxfd + 1, &readset, &writeset, NULL, NULL);

        for(int i = 0; i < nfiles; i++)
        {
            bzero(buf, BUFFERSIZE);
            flags = file[i].f_flags;
            if(flags == 0 || flags & F_DONE)
            {
                continue;
            }
            
            int fd = file[i].f_fd;
            if(flags  & F_CONNECTING && (FD_ISSET(fd, &readset) || FD_ISSET(fd, &writeset)))
            {
                n = sizeof(error);
                if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &n < 0 || error != 0))
                {
                    printf("nonblocking connect failed for %s", file[i].f_name);
                }

                printf("connection established for %s\n",file[i].f_name);
                FD_CLR(fd,&write_set);

                write_get_cmd(&file[i]);
            }
            else if(flags & F_READING && FD_ISSET(fd, &readset))
            {
                if((n = read(fd, buf, BUFFERSIZE)) == 0)
                {
                    printf("end-of-file on %s\n", file[i].f_name);
                    close(fd);

                    file[i].f_flags = F_DONE;
                    FD_CLR(fd, &read_set);

                    nconn = nconn - 1;
                    nlefttoread = nlefttoread - 1;
                }
                else
                {
                    printf("read %d bytes from %s\n",n, file[i].f_name);
                }
            }
        }
    }
    return EXIT_SUCCESS;
}

void start_connect(struct file * fptr)
{
    struct addrinfo * ai;

    ai = host_serv(fptr->f_host, SERV, 0, SOCK_STREAM);
    if(ai == NULL)
    {
        // failure
        return;
    }

    int fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol);
    if(fd < 0)
    {
        // failure
        return;
    }
    fptr->f_fd = fd;
    printf("start connect for %s, fd %d\n",fptr->f_name, fptr->f_fd);

    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    int n = 0;
    if((n = connect(fd, ai->ai_addr, ai->ai_addrlen)) < 0)
    {
        if(errno != EINPROGRESS)
        {
            // failure
            return;
        }
        fptr->f_flags = F_CONNECTING;
        FD_SET(fptr->f_fd, &read_set);
        FD_SET(fptr->f_fd, &write_set);

        if(fd > maxfd)
        {
            maxfd = fd;
        }
    }
    else if(n == 0)
    {
        write_get_cmd(fptr);
    }
}

void home_page(const char * host, const char * fname)
{
    char line[MAXLINES];
    int fd = connect_to_server_char_help(host,SERV); 
    int n  = snprintf(line, sizeof(line), GET_CMD, fname);
    write(fd, line, n);
    for(; ; )
    {
        if((n = read(fd, line, MAXLINES)) == 0)
        {
            break; // server is closed the connection
        }
        printf("read %d bytes of home page\n",n);
        // process data
    }
    printf("end-of-file on home page\n");
    close(fd);
}